home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Amiga Collections: Bavarian
/
Bavarian #197 (19xx)(APS Electronic).zip
/
Bavarian #197 (19xx)(APS Electronic).adf
/
prg.doc
< prev
next >
Wrap
Text File
|
1988-06-01
|
11KB
|
229 lines
AmigaLisp - Programmdokumentation
---------------------------------
Die grundlegenden Datenstrukturen :
Alle Lisp-Daten werden auf dem Heap angelegt. Dazu gibt es einige
Verkettungsstrukturen. Zur Identifizierung erhält jede Struktur eine
Typkennung, die sich stets in der Komponente knotentyp wiederfindet.
atom - nimmt alle Lisp-Atome als Zeichenkette auf. Zahlen werden jedoch
verschlüsselt : zeichenkette[0]=='æ' dient als Kennung und zeichenkette[1]
kann die Werte 'B' für Bruch- oder 'F' für Fließkommazahl enthalten.
Um mit Zahlknoten leichter arbeiten zu können, gibt es für Brüche die
Struktur bruch und für fließkommazahlen die Struktur dezimal. Beide sind
aber Spezielfälle von atom. Wahrheitswerte werden nicht mit Knoten festge-
halten. NIL wird durch einen Nullzeiger und t (TRUE) durch den Zeiger
"wahr" dargestellt. Zu jeder Primitive existiert ein fest angelegter Knoten,
so daß auch hier nur Zeiger verwaltet werden müssen.
Atome erhalten die Typkennung 2.
paar - ist ein Punktepaar-Knoten, d.h. ein Paar von Zeigern auf weitere
Strukturen vom Typ paar oder auf Atom-Knoten. Mit dieser Struktur werden
Listen und Bäume verkettet. Typkennung : 1
varlist - ist ein Knoten zum Aufbau eines binären Variablenbaums. (siehe
Variablenverwaltung) Folglich existieren Zeiger auf den rechten/linken
Teilbaum. Ein weiterer Zeiger weist auf einen atomaren Knoten, der den
Namen der Variable aufnimmt. Der letzte Zeiger bezeichnet den Eintrag des
Symbols. Da lokale Variablen die gleichen Namen wie globale tragen dürfen,
kann ein Symbol mehrfach belegt sein. Daher werden die verschiedenen Werte
in einer stackähnlichen Liste verwaltet. Die Wurzel des Variablenbaums ist
symbolliste. Typkennung : 3
objekt - Das Vektorgrafikmodul ermöglicht die Einrichtung von
Grafikobjekten. Diese Struktur verwaltet ein solches Objekt. Alle Objekte
sind in einer Liste zusammengefaßt, deren Kopfzeiger objektliste ist.
Typkennung : 5
Die Funktionen :
Modul1
Zur Speicherverwaltung
Vom System wird ein 32K-großer Bereich angefordert, der nun selbstständig
verwaltet wird. Dazu werden Marken eingeführt: Zu Beginn jedes Eintrags im
Bereich steht ein Zeiger, der auf den nächsten Eintrag bzw. auf den
nächsten Freiraum weist. Diese Zeiger weisen nur auf gerade Adressen. So
kann das untere Bit als Kennung benutzt werden, ob das nachfolgende Segment
frei oder mit Daten belegt ist (gesetzt = belegt). Das Ende des
selbstverwalteten Speichers wird durch den Zeiger 0xfffffffe markiert.
Reicht der angeforderte Speicher nicht aus, so wird ein weiterer 32K-Block
initialisiert. Zur Verkettung dieser Blöcke wird die Adresse des neuen
Blocks mit 0x80000001 AND-verknüpft und an die Stelle des alten 0xfffffffe-
Zeigers gesetzt. Das Feld segmente verwaltet die 32K-Blöcke.
Die Befehle new und dispose können wie in Turbo-Pascal benutzt werden.
Für die Pufferung von Ein- und Ausgaben werden zusätzlich ebenfalls jeweils
32K reserviert.
Da bei der Abarbeitung eines Lisp-Programms sehr viele unterschiedlich Große
Knoten anfallen, muß eine Garbage-Collection implementiert sein. Diese
gliedert sich in zwei Teile : Tritt ein akuter Speichermangel auf, so werden
zunächst benachbarte freie Speichereinheiten zusammengefaßt. (Funktion
angliedern) Gelengt der Interpreter auf die 0-te Rekursionsebene, so kann
eine komplette Garbage-Collection durchgeführt werden. Dabei werden alle
unnützen Freiräume in den 32K-Segmenten durch Umkopieren behoben. Die
Referenzen der verschobenen Daten werden in allen Knoten geändert.
Ein-/Ausgabe von Listen
Mit den Funktionen output und input können Listen ein- und ausgegeben
werden. Sie stützen sich auf listout, leerweg, atomin und listin ab.
Die Ausgabe kann in eine Datei, auf den Drucker und auf den Bildschirm
gelangen. datprint sorgt für den richtigen Datenfluß.
Die Prozedur edit ruft Funktionen zur Eingabe von der Tastatur auf.
Modul2
Variablenverwaltung
Die Funktionen suchsymbol, searchsymbol, initsymbol und delsymbol lesen,
schreiben oder ändern im Variablenbaum. rekaus und vlist geben alle
gespeicherten Symbolnamen aus.
ALisp-Primitive
Die Namen der Lisp-Befehle werden in initbefehl festgelegt. Hier werden sie
auch alphabetisch sortiert, damit spätere Suchen binär und damit schneller
geschehen können. Die Anzahl der Befehle muß im Macro anzahl festgelegt
sein. Dieses findet sich sowohl in Modul2 als auch in Modul1. Die Anzahl muß
stimmen! Die Funktion befehl prüft, ob ein Befehl vorliegt.
Auswertung von Lisp-Ausdrücken
evaluate ist die Kernprozedur des Interpreters. Hier wird geprüft, ob ein
Atom oder ob eine Liste ausgewertet werden soll. Bei Atomen werden als Ergeb-
nis der Auswertung diese ohne Veränderung wieder zurückgegeben. Nur Symbole
bilden eine Ausnahme : Hier wird ihr Wert mitgeteilt, bei Primitiven deren
Nummern. Soll eine Liste ausgewertet werden, so muß das erste Listenelement
zu einem Lambda- oder Macro- Term auswerten oder aber eine Primitive sein.
Die zu den Primitiven gehörenden Bearbeitungsprozeduren werden nun über eine
Sprungtabelle erreicht.
main ist das Hauptprogramm. Hier werden Parameter beim Aufruf des
Interpreters gelesen und hier liegt auch die Eingabe-Auswertung-Ausgabe -
Schleife, die das Grundprinzip von Lisp-Interpretern ist.
Modul3
Zunächst findet sich hier eine Tabelle aller Fehlermeldungen. Dann folgt die
Funktion genug_parameter, die überprüft, ob eine Lisp-Primitive genug
Parameter erhält. Danach folgen die grundlegenden Primitiven. Ihre
Implementation ist für sich betrachtet jeweils leicht zu durchschauen.
Deshalb möchte ich hier nur den prinzipiellen Aufbau eines Befehls
besprechen.
Soll ein neuer Befehl hinzugefügt werden, so müssen die beiden anzahl-Macros
um 1 erhöht werden. In Modul2 wird die Realisierungsprozedur als extern
gekennzeichnet. Der Name des neuen Befehls wird an die Tabelle in initbefehl
gehängt und der Aufruf der Prozedur wird an das Ende der Sprungtabelle in
evaluate geschrieben. evaluate übergibt nun einen Zeiger auf die Liste aller
Parameter, die mit atom und paar aufgebaut ist. Bei der Abarbeitung dieser
Liste muß dafür gesorgt werden, daß keine hängenden Zeiger entstehen, d.h.
wenn die Listenelemente nicht zu neuen Strukturen verarbeitet werden,
sollten sie gelöscht werden. Jede Befehlsfunktion muß einen Zeiger auf ein
Atom oder auf eine Liste zurückgeben. Soll eine Primitive ohne Rückgabewert
definiert werden, so ist der Zeiger leeres_atom zurückzugeben.
---------Beispiel der Implementierung einer Befehlsfunktion:-----------
struct atom *XY(parameter)
struct paar *parameter;
{ if(genug_parameter(parameter,2,3))
{ struct atom *hilf;
struct paar *merke;
hilf=evaluate(parameter->CAR);
merke=parameter;parameter=parameter->CDR;dispose(merke);
[...]
return(leeres_atom);
}
else
{ delete(parameter);
return(-1L);
}
}
-----------------------------------------------------------------------
Die Primitive erwartet mindestens zwei und höchstens 3 Parameter. Ist diese
Voraussetzung nicht erfüllt, so wird die Parameterliste gelöscht. Dazu dient
delete, delete löscht stets einen kompletten Lisp-Baum - dispose hingegen
löscht genau einen Knoten. Ein Zeiger mit dem Wert -1L weist immer auf einen
Fehler hin.
Stimmen die Voraussetzungen, so wird der erste Parameter ausgewertet, und
anschließend wird die Parameterliste entsprechend verkürzt.
In Modul3 werden auch Zahlenformate umgewandelt. Mit den Befehlen getfloat
und getint können aus Zahlknoten Ganzzahl- oder Fließkommazahlen gelesen
werden. Weiterhin ist hier die Bruchrechnung implementiert.
Modul4
Hier finden sich die Lisp-Kontrollstrukturen. Die zugehörigen Funktionen
sind aufgebaut wie Befehlsfunktionen. Die Umsetzung wird leicht
verständlich, wenn man die Definition dieser Programmelemente in ALisp.doc
berücksichtigt. Noch eine Bemerkung zu den Schleifen : Bei jedem
Schleifendurchlauf wird eine Kopie des Schleifenrumpfes erstellt, die dann
abgearbeitet wird. Dadurch benötigen Schleifen etwa ebensoviel Speicherplatz
wie die entsprechenden rekursiven Lösungen.
Modul5
Die Stringbefehle sind denen von Basic und XLisp nachempfunden.
Modul6 und Modul7
In diesen Moduln sind alle Grafikbefehle definiert. Wird der Grafikmodus
eingeschaltet, so erscheint ein neues Fenster. Dazu wird aber im Hintergrund
noch ein Screen geöffnet, der als Puffer für den Fill-Befehl sowie als
Zwischenspeicher für Vektorgrafikfilme dient.
Interessant ist vornehmlich Modul7:
Die Grafikobjekte bestehen aus einer Liste von Polygonzügen, Punkten und
Strecken. Ein Grafikobjekt wird mit defobj angelegt. Dabei wird die
übergebene Baumstruktur in ein Feld dynamischer Länge übersetzt, das Teil
der Struktur objekt ist. Auf die Grafikobjekte können lineare Abbildungen
zur Rotation und Streckung sowie eine Abbildung zur Translation angewendet
werden. Diese Abbildungen werden durch Matrizen ausgedrückt, die in
initmatrix vordefiniert sind. Hintereinanderausführung von Abbildung
bedeutet nun Multiplikation der entsprechenden Matrizen. So werden bei den
entsprechenden Befehlen nur Steuersequenzen auf den Stack "operationen" des
Grafikobjekts gelegt. Erst wenn es dargestellt werden muß, werden die zu den
Sequenzen gehörenden Matrizen erstellt, verknüpft und dann auf die einzelnen
Punkte der Polygonzüge angewendet. Die Funktionen zusammenfassen, vemult und
polymult sowie plotten beschreiben diese Tätigkeiten.
Linien, die den Fensterrand überschreiten, müssen entsprechend abgeschnitten
werden. Dafür sorgt in der Prozedur linie der Cohen-Sutherland-Clipping-
Algorithmus. Die Grafik wird durch das Fenster des Moitors betrachtet. Die
Konstante zoom gibt die Entfernung des Beobachters zum Bildschirm an.
Linien, die durch das Bildschirmfenster ragen, werden in polymult ebenfalls
gekürzt.
Die Liste der Grafikobjekte kann mit objlist ausgegeben werden.
Ein HiddenLine-Algorithmus ist bereits eingebaut, wird aber noch nicht
benutzt, da bisher keine ausgefüllten Flächen gezeichnet werden können.
Die Funktion ma_aus dient nur dazu, Matrizen zu Wartungszwecken auszugeben.
Modul8
Die Benutzerschnittstelle macht Gebrauch von den vielfältigen Möglichkeiten
des Amiga-Betriebssystems. Soll ALisp auf andere Rechner portiert werden, so
wird sich hier die Hauptarbeit ergeben :
Zunächst werden die Pulldownmenüs definiert, anschließend die beiden Fenster
und Mauszeiger. nprint gibt Texte im Dialogfenster aus, rprint stellt sie
zusätzlich noch in einer Warnfarbe dar. req liest einen Filenamen über einen
Requester ein. doscommand öffnet ein neues CLI-Fenster. coninit bindet die
Benutzeroberfläche ins System ein. menu fragt die Menüs ab, transform
wandelt RAWKEY-Codes in Ascii-Daten um. cursorcontrol steuert den Cursor im
Eingabefenster, escape prüft den Menüpunkt "Evaluierung abbrechen" und
newcon liest schließlich aus dem Eingabefenster einen Text ein und verbindet
die anderen Interaktionsmöglichkeiten. conreinit schließt die
Benutzeroberfläche.
Zur Compilierung :
Das Programm erwartet Integer-Zahlen der Länge 4 Bytes. Bei vielen
Zeigerzuweisungen treten Typkonflikte auf, die aber vom C-Compiler korrekt
behandelt werden. Die Mathe - Library m.lib muß hinzugelinkt werden.